/*
 * $Id: tcp.c,v 1.36 2006-02-08 11:04:01 bacon Exp $
 */

#include <xp/net/tcp.h>
#include <xp/bas/memory.h>
#include <xp/bas/utf8.h>
#include <xp/bas/assert.h>
#include <xp/bas/errno.h>

static xp_tcp_t* __tcp_open (
	xp_tcp_t* shell, xp_tcp_t* tcp, xp_sck_t* sck, int mode);
static xp_ssize_t __tcp_input (
	int cmd, void* arg, void* buf, xp_size_t size);
static xp_ssize_t __tcp_output (
	int cmd, void* arg, void* buf, xp_size_t size);

xp_tcp_t* xp_tcp_open (xp_tcp_t* tcp, int mode)
{
	return __tcp_open (XP_NULL, tcp, XP_NULL, mode);
}

static xp_tcp_t* __tcp_open (
	xp_tcp_t* shell, xp_tcp_t* tcp, xp_sck_t* sck, int mode)
{
	if (tcp == XP_NULL) {
		tcp = (xp_tcp_t*)xp_malloc (xp_sizeof(xp_tcp_t));
		if (tcp == XP_NULL) {
			if (shell != XP_NULL) shell->errnum = XP_TCP_ENOMEM;
			return XP_NULL;
		}
		tcp->__dynamic = xp_true;
	}
	else tcp->__dynamic = xp_false;

	if (mode == XP_TCP_TEXT) {
		if (xp_tio_open(&tcp->tio) == XP_NULL) {
			int num = xp_geterrno();

			if (shell != XP_NULL) shell->errnum = XP_TCP_ETIOOP;
			if (tcp->__dynamic) xp_free (tcp);

			xp_seterrno (num);
			return XP_NULL;
		}

		if (xp_tio_attinp(&tcp->tio, __tcp_input, tcp) == -1) {
			int num = xp_geterrno();

			if (shell != XP_NULL) shell->errnum = XP_TCP_ETIOAI;
			xp_tio_close (&tcp->tio);
			if (tcp->__dynamic) xp_free (tcp);

			xp_seterrno (num);
			return XP_NULL;
		}
		if (xp_tio_attoutp(&tcp->tio, __tcp_output, tcp) == -1) {
			int num = xp_geterrno();

			if (shell != XP_NULL) shell->errnum = XP_TCP_ETIOAO;
			xp_tio_close (&tcp->tio);
			if (tcp->__dynamic) xp_free (tcp);

			xp_seterrno (num);
			return XP_NULL;
		}
	}
	
	tcp->sck = sck;
	tcp->mode = mode;
	tcp->errnum = XP_TCP_ENOERR;
	return tcp;
}

int xp_tcp_close (xp_tcp_t* tcp)
{
	xp_assert (tcp != XP_NULL);

	if (tcp->mode == XP_TCP_TEXT) {
		if (xp_tio_close (&tcp->tio) == -1) {
			tcp->errnum = XP_TCP_ETIOCL;
			return -1;
		}

		/* If xp_tcp_close fails somewhere below here, it might be
		 * called again explicitly for some reasons. but xp_tio_close 
		 * should not be called again. It is just a work-around to 
		 * change tcp->mode to XP_TCP_BINARY to prevent it from being
		 * called again.
		 */
		tcp->mode = XP_TCP_BINARY;
	}

	if (tcp->sck != XP_NULL) {
		if (xp_sck_close(tcp->sck) == -1) {
			tcp->errnum = XP_TCP_ESCKCL;
			return -1;
		}
		else tcp->sck = XP_NULL;
	}

	if (tcp->__dynamic) xp_free (tcp);
	return 0;
}

xp_sckhnd_t xp_tcp_gethnd (xp_tcp_t* tcp)
{
	xp_assert (tcp != XP_NULL);
	return xp_sck_gethnd (tcp->sck);
}

int xp_tcp_geterrnum (xp_tcp_t* tcp)
{
	xp_assert (tcp != XP_NULL);
	return tcp->errnum;
}

const xp_char_t* xp_tcp_geterrstr (xp_tcp_t* tcp)
{
	static const xp_char_t* __errstr[] =
	{
		XP_TEXT("no error"),
		XP_TEXT("out of memory"),
		XP_TEXT("invalid argument, data, etc"),
		XP_TEXT("operation timed out"),
		XP_TEXT("socket open failure"),
		XP_TEXT("socket close failure"),
		XP_TEXT("socket connect failure"),
		XP_TEXT("socket bind failure"),
		XP_TEXT("socket listen failure"),
		XP_TEXT("socket accept failure"),
		XP_TEXT("socket send failure"),
		XP_TEXT("socket receive failure"),
		XP_TEXT("tio open failure"),
		XP_TEXT("tio close failure"),
		XP_TEXT("tio input attach failure"),
		XP_TEXT("tio output attach failure"),
		XP_TEXT("unknown error")
	};

	xp_assert (tcp != XP_NULL);
	return __errstr[
		(tcp->errnum < 0 || tcp->errnum >= xp_countof(__errstr))?
		xp_countof(__errstr) - 1: tcp->errnum];
}

xp_time_t xp_tcp_gettmout (xp_tcp_t* tcp, int object)
{
	xp_assert (tcp != XP_NULL);
	return xp_sck_gettmout (tcp->sck, object);
}

void xp_tcp_settmout (xp_tcp_t* tcp, int object, xp_time_t tmout)
{
	xp_assert (tcp != XP_NULL);
	xp_sck_settmout (tcp->sck, object, tmout);
}

void xp_tcp_setcancel (xp_tcp_t* tcp, 
	int object, xp_bool_t (*cancel) (void*), void* arg)
{
	xp_sck_setcancel (tcp->sck, object, cancel, arg);
}

int xp_tcp_conn (xp_tcp_t* tcp, xp_uint16_t port,
	const void* host, xp_size_t hostsz, xp_uint32_t scope_id)
{
	int n;
	xp_sckadrin4_t sin;
	xp_sckadrin6_t sin6;

	xp_assert (tcp != XP_NULL);

	if (tcp->sck != XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	switch (hostsz) {
	case xp_sizeof(sin.sin_addr.s_addr):
		if (xp_sizeof(sin.sin_addr.s_addr) != hostsz) {
			tcp->errnum = XP_TCP_EINVAL;
			return -1;
		}

		tcp->sck = xp_sck_open (
			&tcp->__sck, AF_INET, SOCK_STREAM, 0);
		if (tcp->sck == XP_NULL) {
			tcp->errnum = XP_TCP_ESCKOP;
			return -1;
		}

		xp_memset (&sin, 0, xp_sizeof(sin));
		sin.sin_family = AF_INET;
		xp_memcpy (&sin.sin_addr.s_addr, host, hostsz);
		sin.sin_port = port;
		n = xp_sck_conn (
			tcp->sck, (xp_sckadr_t*)&sin, xp_sizeof(sin));
		break;

	case xp_sizeof(sin6.sin6_addr.s6_addr):
		if (xp_sizeof(sin6.sin6_addr.s6_addr) != hostsz) {
			tcp->errnum = XP_TCP_EINVAL;
			return -1;
		}

		tcp->sck = xp_sck_open (
			&tcp->__sck, AF_INET6, SOCK_STREAM, 0);
		if (tcp->sck == XP_NULL) {
			tcp->errnum = XP_TCP_ESCKOP;
			return -1;
		}

		xp_memset (&sin6, 0, xp_sizeof(sin6));
		sin6.sin6_family = AF_INET6;
		sin6.sin6_port = port;
#ifdef XP_HAVE_IN6_SCOPE_ID
		sin6.sin6_scope_id = scope_id;
#endif
		xp_memcpy (&sin6.sin6_addr.s6_addr, host, hostsz);
		n = xp_sck_conn (
			tcp->sck, (xp_sckadr_t*)&sin6, xp_sizeof(sin6));
		break;

	default:
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (n == -1) {
		int num = xp_geterrno();

		tcp->errnum = (num == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKCN;

		xp_sck_close (tcp->sck);
		tcp->sck = XP_NULL;	

		xp_seterrno (num);
		return -1;
	}

	return 0;
}

int xp_tcp_bind (xp_tcp_t* tcp, xp_uint16_t port,
	const void* host, xp_size_t hostsz, xp_uint32_t scope_id)
{
	int n;
	xp_sckadrin4_t sin;
	xp_sckadrin6_t sin6;

	xp_assert (tcp != XP_NULL);

	if (tcp->sck != XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	switch (hostsz) {
	case xp_sizeof(sin.sin_addr.s_addr):
		if (xp_sizeof(sin.sin_addr.s_addr) != hostsz) {
			tcp->errnum = XP_TCP_EINVAL;
			return -1;
		}

		tcp->sck = xp_sck_open (
			&tcp->__sck, AF_INET, SOCK_STREAM, 0);
		if (tcp->sck == XP_NULL) {
			tcp->errnum = XP_TCP_ESCKOP;
			return -1;
		}

		n = 1;
		xp_sck_setopt (tcp->sck,
			SOL_SOCKET, SO_REUSEADDR, &n, xp_sizeof(n));

		xp_memset (&sin, 0, xp_sizeof(sin));
		sin.sin_family = AF_INET;
		sin.sin_port = port;
		xp_memcpy (&sin.sin_addr.s_addr, host, hostsz);
		n = xp_sck_bind (
			tcp->sck, (xp_sckadr_t*)&sin, xp_sizeof(sin));
		break;

	case xp_sizeof(sin6.sin6_addr.s6_addr):
		if (xp_sizeof(sin6.sin6_addr.s6_addr) != hostsz) {
			tcp->errnum = XP_TCP_EINVAL;
			return -1;
		}

		tcp->sck = xp_sck_open (
			&tcp->__sck, AF_INET6, SOCK_STREAM, 0);
		if (tcp->sck == XP_NULL) {
			tcp->errnum = XP_TCP_ESCKOP;
			return -1;
		}

		n = 1;
		xp_sck_setopt (tcp->sck, 
			SOL_SOCKET, SO_REUSEADDR, &n, xp_sizeof(n));

		xp_memset (&sin6, 0, xp_sizeof(sin6));
		sin6.sin6_family = AF_INET6;
		sin6.sin6_port = port;
#ifdef XP_HAVE_IN6_SCOPE_ID
		sin6.sin6_scope_id = scope_id;
#endif
		xp_memcpy (&sin6.sin6_addr.s6_addr, host, hostsz);
		n = xp_sck_bind (
			tcp->sck, (xp_sckadr_t*)&sin6, xp_sizeof(sin6));
		break;

	default:
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (n == -1) {
		int num = xp_geterrno();

		tcp->errnum = (num == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKBN;

		xp_sck_close (tcp->sck);
		tcp->sck = XP_NULL;	

		xp_seterrno (num);
		return -1;
	}
	
#ifdef IPV6_V6ONLY
	{
		int n = 1;
		if (hostsz == xp_sizeof(sin6.sin6_addr.s6_addr)) {
			xp_sck_setopt (tcp->sck, 
				IPPROTO_IPV6, IPV6_V6ONLY, &n, xp_sizeof(n));
		}
	}
#endif

	return 0;
}

int xp_tcp_lstn (xp_tcp_t* tcp, int backlog)
{
	xp_assert (tcp != XP_NULL);

	if (tcp->sck == XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (xp_sck_lstn (tcp->sck, backlog) == -1) {
		tcp->errnum = XP_TCP_ESCKLS;
		return -1;
	}

	return 0;
}

xp_tcp_t* xp_tcp_acpt (xp_tcp_t* tcp, xp_tcp_t* client)
{
	xp_sckadrin4_t sin;
	xp_sckadrin6_t sin6;
	xp_sckadr_t* adr;
	xp_scklen_t len;
	xp_sck_t* newsck;

	xp_assert (tcp != XP_NULL);

	if (tcp->sck == XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return XP_NULL;
	}

	if (xp_sizeof(sin6) > xp_sizeof(sin)) {
		adr = (xp_sckadr_t*)&sin6;
		len = xp_sizeof(sin6);
	}
	else {
		adr = (xp_sckadr_t*)&sin;
		len = xp_sizeof(sin);
	}

	if (client == XP_NULL) {
		newsck = xp_sck_acpt (tcp->sck, XP_NULL, adr, &len);
	}
	else {
		newsck = xp_sck_acpt (tcp->sck, &client->__sck, adr, &len);
	}

	if (newsck == XP_NULL) {
		tcp->errnum = (xp_geterrno() == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKAC;
		return XP_NULL;
	}

	return __tcp_open (tcp, client, newsck, tcp->mode);
}

xp_ssize_t xp_tcp_send (xp_tcp_t* tcp, const void* data, xp_size_t size)
{
	xp_ssize_t n;

	xp_assert (tcp != XP_NULL);

	if (tcp->sck == XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (tcp->mode == XP_TCP_BINARY) {
		n = xp_sck_send (tcp->sck, data, size, 0);
	}
	else {
		xp_assert (tcp->mode == XP_TCP_TEXT);
		n = xp_tio_putsx(&tcp->tio, (const xp_char_t*)data, size);
		if (xp_tio_flush(&tcp->tio) == -1) n = -1;
	}

	if (n == -1) {
		tcp->errnum = (xp_geterrno() == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKSN;
		return -1;
	}

	return n;
}

xp_ssize_t xp_tcp_recv (xp_tcp_t* tcp, void* buf, xp_size_t size)
{
	xp_ssize_t n;

	xp_assert (tcp != XP_NULL);

	if (tcp->sck == XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (tcp->mode == XP_TCP_BINARY) {
		n = xp_sck_recv (tcp->sck, buf, size, 0);
	}
	else {
		xp_assert (tcp->mode == XP_TCP_TEXT);
		n = xp_tio_getsx (&tcp->tio, (xp_char_t*)buf, size);
	}

	if (n == -1) {
		tcp->errnum = (xp_geterrno() == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKRC;
		return -1;
	}

	return n;
}

static xp_ssize_t __tcp_input (int cmd, void* arg, void* buf, xp_size_t size)
{
	xp_tcp_t* tcp = (xp_tcp_t*)arg;

	xp_assert (tcp != XP_NULL);
	if (cmd == XP_TIO_IO_DATA) {
		xp_assert (tcp->sck != XP_NULL);
		return xp_sck_recv (tcp->sck, buf, size, 0);
	}

	return 0;
}

static xp_ssize_t __tcp_output (int cmd, void* arg, void* buf, xp_size_t size)
{
	xp_tcp_t* tcp = (xp_tcp_t*)arg;

	xp_assert (tcp != XP_NULL);
	if (cmd == XP_TIO_IO_DATA) {
		xp_assert (tcp->sck != XP_NULL);
		return xp_sck_send (tcp->sck, buf, size, 0);
	}

	return 0;
}

